HIDL Hal 开发指南8 —— 简单 HIDL HAL 实现

3/29/2024

HIDL HAL 的整体架构如下图所示:

20240327222829

上一节我们实现了一个简单的驱动,本节继续从下往上实现一个简单的 HIDL HAL 模块

这部分内容主要是在HIDL Hal 开发指南3 —— HIDL HAL 实例程序 (opens new window)基础上做一些修改,添加驱动访问的代码。

# Hal 文件实现

Hal 层的实现一般放在 hardware、vendor 或者 device 目录下。

我们的示例就放在 vendor 目录下。

创建目录:

# 系统源码目录下
mkdir -p jelly/hardware/interfaces/hello_hidl/1.0
1
2

接着在 vendor/jelly/hardware/interface/hello_hidl/1.0 目录下创建 Hal 文件

//定义包名,最后跟一个版本号
package jelly.hardware.hello_hidl@1.0;
//定义 hidl 服务对外提供的接口
interface IHello {
    //for test,generates 后面跟的是返回类型
    addition_hidl(uint32_t a,uint32_t b) generates (uint32_t total);
    //写 hello 驱动
    write(string name) generates (uint32_t result);
    //读 hello 驱动
    read() generates (string name);
};
1
2
3
4
5
6
7
8
9
10
11

这里的 IHello.hal 定义了我们的服务对外提供了哪些函数。可以认为这就是我们服务的对外协议。协议一般定义好就不会再修改,以保持对外的稳定性。 关于 hal 的写法,可以参考官方的文档 (opens new window),另外也可以参考 hardware 目录下系统自带的 hal 的写法。

# Hal 文件生成 CPP 代码

接着我们使用 hidl-gen 命令将我们写的 hal 文件转换为 C++ 文件:

在系统源码下依次执行下面的命令:

source build/envsetup.sh
# 项目的完整包名
PACKAGE=jelly.hardware.hello_hidl@1.0 
# 生成代码的存放位置
LOC=vendor/jelly/hardware/interfaces/hello_hidl/1.0/default
# -o 选项指定生成的文件存放的位置
# -Lc++-impl 表示要生成 C++ 代码
# -rjelly.hardware:vendor/jelly/hardware/interfaces 用于指定包名与路径的对应关系
# 最后的 $PACKAGE 指定项目的完整的包名
hidl-gen -o $LOC -Lc++-impl -rjelly.hardware:vendor/jelly/hardware/interfaces $PACKAGE
1
2
3
4
5
6
7
8
9
10

执行完上面的命令后,在 vendor/jelly/hardware/interfaces/hello_hidl/1.0/default 目录下会生成 Hello.cpp 和 Hello.h。

接着修改 vendor/jelly/hardware/interfaces/hello_hidl/1.0/default 目录下生成的 Hello.cpp:

// FIXME: your file license if you have one

#include "Hello.h"
#include <log/log.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

namespace jelly {
namespace hardware {
namespace hello_hidl {
namespace V1_0 {
namespace implementation {

// Methods from ::jelly::hardware::hello_hidl::V1_0::IHello follow.
Return<uint32_t> Hello::addition_hidl(uint32_t a, uint32_t b) {
    ALOGE("hello_hidl service is init success....a :%d,b:%d",a,b);
    return uint32_t {};
}

Return<uint32_t> Hello::write(const hidl_string& name) {

    int fd = open("/dev/hello", O_RDWR);
    if (fd == -1)
	{
		printf("can not open file /dev/hello\n");
		return  uint32_t { 0 };
	}

    ::write(fd, name.c_str(), 100);

    close(fd);
    return uint32_t { 1 };
}

//通过 read_cb function 回调函数,回传数据
Return<void> Hello::read(read_cb _hidl_cb) {
    char buf[100];
    int fd = open("/dev/hello", O_RDWR);
    if (fd == -1)
	{
		printf("can not open file /dev/hello\n");
		return Void();
	}

    ::read(fd, buf, 100);

    hidl_string result(buf);
    _hidl_cb(result);

    close(fd);
    return Void();
}

}  // namespace implementation
}  // namespace V1_0
}  // namespace hello_hidl
}  // namespace hardware
}  // namespace jelly

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

这里主要是对我们的协议进行实现,实现了对上一节实现的设备文件 /dev/hello 的读写。至此我们的 hidl 服务就定义好了

# 服务端实现

接着我们需要写一个 Server 端来向 HwServiceManager 注册我们的服务。在 vendor/jelly/hardware/interfaces/hello_hidl/1.0/default 目录下添加 service.cpp:

#include <hidl/HidlTransportSupport.h>
#include <utils/Looper.h>
#include <utils/StrongPointer.h>
#include <log/log.h>
#include "Hello.h"

using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;
using jelly::hardware::hello_hidl::V1_0::IHello;
using jelly::hardware::hello_hidl::V1_0::implementation::Hello;

int main() {
    ALOGD("hello-hidl is starting...");

    // 配置 Binder 的线程数
    configureRpcThreadpool(4, true /* callerWillJoin */);

    // 初始化一个 Hello 服务端对象
    android::sp<IHello> service = new Hello();
    // 注册服务
    android::status_t ret = service->registerAsService();

    if (ret != android::NO_ERROR) {
    }

    // 当前线程成为 HwBinder 线程
    joinRpcThreadpool();

    return 0;
    //Passthrough模式
    //return defaultPassthroughServiceImplementation<IHello>(4);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

这里使用的是 libhwbinder 库来做实现,接口上与 libbinder 库大体类似。

我们的服务端需要在开机时启动,创建 vendor/jelly/hardware/interfaces/hello_hidl/1.0/default/jelly.hardware.hello_hidl@1.0-service.rc 文件:

service vendor_hello_hidl_service /vendor/bin/hw/jelly.hardware.hello_hidl@1.0-service
	class hal
	user system
	group system
1
2
3
4

接着我们需要添加 VINTF 对象,对于注册到 hwservicemanager 的服务都需要添加一个 VINTF 对象。对于编码来说 VINTF 对象就是一个 xml 文件,创建 vendor/jelly/hardware/interfaces/hello_hidl/1.0/default/jelly.hardware.hello_hidl@1.0-service.xml 文件:

<manifest version="1.0" type="device">
  <hal format="hidl">
        <name>jelly.hardware.hello_hidl</name>
        <transport>hwbinder</transport>
        <version>1.0</version>
        <interface>
            <name>IHello</name>
            <instance>default</instance>
        </interface>
    </hal>
</manifest>
1
2
3
4
5
6
7
8
9
10
11

上面的这种添加 VINTF 对象的方式仅在 Andorid 11 及以后才支持。我们当前版本是 Android10,需要修改 device/generic/goldfish/manifest.xml,在其中添加:

    <hal format="hidl">
        <name>jelly.hardware.hello_hidl</name>
        <transport>hwbinder</transport>
        <version>1.0</version>
        <interface>
            <name>IHello</name>
            <instance>default</instance>
        </interface>
    </hal>
1
2
3
4
5
6
7
8
9

接着我们使用 hidl-gen 命令来生成对应的 Android.bp 文件:

# 注意和前面使用同一个终端
hidl-gen -o $LOC -Landroidbp-impl -rjelly.hardware:vendor/jelly/hardware/interfaces $PACKAGE
1
2

这个命令会在 vendor/jelly/hardware/interfaces/hello_hidl/1.0/default 目录下生成一个 Android.bp,我们在生成的基础上稍作修改如下:

// FIXME: your file license if you have one

cc_library_shared {
    name: "jelly.hardware.hello_hidl@1.0-impl",
    relative_install_path: "hw",
    proprietary: true,
    srcs: [
        "Hello.cpp",
    ],
    shared_libs: [
        "libhidlbase",
        "libhidltransport",
        "libutils",
        "jelly.hardware.hello_hidl@1.0",
        "liblog",
    ],
}

cc_binary {
    name: "jelly.hardware.hello_hidl@1.0-service",
    init_rc: ["jelly.hardware.hello_hidl@1.0-service.rc"],
    // 这种方式添加 vintf ,在 Android11 以后才支持
    vintf_fragments: ["jelly.hardware.hello_hidl@1.0-service.xml"],
    defaults: ["hidl_defaults"],
    relative_install_path: "hw",
    vendor: true,
    srcs: ["service.cpp", "Hello.cpp"],
    shared_libs: [
        "jelly.hardware.hello_hidl@1.0",
        "libhardware",
        "libhidlbase",
        "libhidltransport",
        "libutils",
        "liblog",
    ],
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

上面生成的 Android.bp 里面有一个依赖 jelly.hardware.hello_hidl@1.0,目前编译系统中还没有这个库,接着我们来生成 jelly.hardware.hello_hidl@1.0 库对应的 Android.bp。

hardware/interfaces 目录下,将 update-makefiles.sh 拷贝到 vendor/jelly/hardware/interfaces/ 目录下,并修改如下:

#!/bin/bash

source $ANDROID_BUILD_TOP/system/tools/hidl/update-makefiles-helper.sh

do_makefiles_update \
  "jelly.hardware:vendor/jelly/hardware/interfaces"
1
2
3
4
5
6

接着在系统源码目录下执行:

./vendor/jelly/hardware/interfaces/update-makefiles.sh
1

就会生成 vendor/jelly/hardware/interfaces/hello_hidl/1.0/Android.bp

hidl_interface {
    name: "jelly.hardware.hello_hidl@1.0",
    root: "jelly.hardware",
    product_specific: true,
    srcs: [
        "IHello.hal",
    ],
    interfaces: [
        "android.hidl.base@1.0",
    ],
    gen_java: true,
}
1
2
3
4
5
6
7
8
9
10
11
12

其中的 hidl_interface 是 hidl 独有的,当编译源码时,它会将 out/soong/.intermediates/vendor/jelly/hardware/interfaces/hello_hidl/1.0/jelly.hardware.hello_hidl@1.0_genc++/gen/jelly/hardware/hello_hidl/1.0 和 out/soong/.intermediates/vendor/jelly/hardware/interfaces/hello_hidl/1.0/jelly.hardware.hello_hidl@1.0_genc++_headers/gen/jelly/hardware/hello_hidl/1.0 目录下的源码编译为 jelly.hardware.hello_hidl@1.0.so 文件,并预制到手机的 /vendor/lib 和 /vendor/lib64/ 目录下。

为了使编译通过,新建 vendor/jelly/hardware/interfaces/Android.bp 文件:

hidl_package_root {
    name: "jelly.hardware",
    path: "vendor/jelly/hardware/interfaces",
}
1
2
3
4

这个 Android.bp 的作用是告诉编译系统包名与路径的映射关系。

接着新建 vendor/jelly/hardware/interfaces/current.txt 文件,current.txt 记录了所有 hal 接口的 hash 值,接口有变化时,同时需要更新 current.txt 中的 hash 值,这是我们先随便设置一个 hash 值:

123456 jelly.hardware.hello_hidl@1.0::IHello
1

再执行一遍 update-makefiles.sh,这个时候就会发现提示 hash 值不正确了,同时会给出正确的 hash 值,我们把正确的 hash 值替换到 current.txt 即可。

# 客户端实现

在 vendor/jelly/hardware/interfaces/hello_hidl/1.0/default 目录下创建如下的文件和文件夹:

20240328163932

其中 hello_hidl_test.cpp



#define LOG_TAG "hello_hidl"
#include <log/log.h>

using android::sp;
using jelly::hardware::hello_hidl::V1_0::IHello;
using android::hardware::Return;
using android::hardware::hidl_string;

int main(){
    // 获取服务代理端对象
    android::sp<IHello> hw_device = IHello::getService();
    if (hw_device == nullptr) {
              ALOGD("failed to get hello-hidl");
              return -1;
        }
    ALOGD("success to get hello-hidl....");
    // 通过代理端对象发起远程调用
    Return<uint32_t> total = hw_device->addition_hidl(3,4);
    hw_device->write("hello");
    hw_device->read([&](hidl_string result){
        ALOGD("%s\n", result.c_str());
    });
    return 0;
} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

这里使用的是 libhwbinder 接口,与 binder 大体一致。

Android.bp:

cc_binary {
    name: "hello_hidl_test",
    srcs: ["hello_hidl_test.cpp"],
    vendor: true,
    shared_libs: [
        "liblog",
        "jelly.hardware.hello_hidl@1.0",
        "libhidlbase",
        "libhidltransport",
        "libhwbinder",
        "libutils",
    ],
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14

# selinux 配置

在 device/Jelly/Rice14/sepolicy 目录下添加:

hwservice.te:

type hello_hidl_hwservice, hwservice_manager_type;
1

hello_hidl.te:

type hello_hidl, domain;
type hello_hidl_exec, exec_type, vendor_file_type, file_type;

init_daemon_domain(hello_hidl);
add_hwservice(hello_hidl, hello_hidl_hwservice)
hwbinder_use(hello_hidl)

allow hello_hidl hello_hidl_hwservice:hwservice_manager { add };
allow hello_hidl hello_dev_t:chr_file { open read write };
binder_call(hello_hidl,hwservicemanager)
get_prop(hello_hidl,hwservicemanager_prop)

1
2
3
4
5
6
7
8
9
10
11
12

hwservice_contexts:

jelly.hardware.hello_hidl::IHello      u:object_r:hello_hidl_hwservice:s0
1

hello_hidl_test.te:

type  hello_hidl_test, domain;
type  hello_hidl_test_exec, exec_type, vendor_file_type, file_type;

domain_auto_trans(shell, hello_hidl_test_exec, hello_hidl_test);

get_prop(hello_hidl_test, hwservicemanager_prop)
allow hello_hidl_test hello_hidl_hwservice:hwservice_manager find;
hwbinder_use(hello_hidl_test);
1
2
3
4
5
6
7
8

在 file_contexts 中添加:

/vendor/bin/hw/jelly\.hardware\.hello_hidl@1\.0-service    u:object_r:hello_hidl_exec:s0
/vendor/bin/hello_hidl_test     u:object_r:hello_hidl_test_exec:s0
1
2

# 编译执行

接着在 device/Jelly/Rice14/Rice14.mk 中添加如下内容:

BOARD_SEPOLICY_DIRS += \
    device/Jelly/Rice14/sepolicy

PRODUCT_PACKAGES += \
    jelly.hardware.hello_hidl@1.0-service \
    hello_hidl_test \
    jelly.hardware.hello_hidl@1.0-impl \
1
2
3
4
5
6
7

然后整编系统:

source build/envsetup.sh
lunch rice14-eng
make -j32
emulator -kernel ~/Project/kernel/goldfish/arch/x86_64/boot/bzImage
1
2
3
4

最后测试:

adb shell
# 执行客户端程序
hello_hidl_test &
# 查看 log
logcat | grep hello 
3-28 18:31:40.460  1542  1542 D         : hello-hidl is starting...
03-28 18:31:40.461  1542  1542 I ServiceManagement: Registered jelly.hardware.hello_hidl@1.0::IHello/default (start delay of 77ms)
03-28 18:31:40.461  1542  1542 I ServiceManagement: Removing namespace from process name jelly.hardware.hello_hidl@1.0-service to hello_hidl@1.0-service.
03-28 18:32:35.299  3050  3050 D hello_hidl: success to get hello-hidl....
03-28 18:32:35.299  1542  1561 D HELLO_HAL: write hello
03-28 18:32:35.300  3050  3050 D hello_hidl: test
1
2
3
4
5
6
7
8
9
10
11